//SPDX-FileCopyrightText: Copyright 2022-2024 深圳市同心圆网络有限公司
//SPDX-License-Identifier: GPL-3.0-only

package requirement_dao

import (
	"context"
	"regexp"

	"atomgit.com/openlinksaas/api-server/dao/db_ns"
	"atomgit.com/openlinksaas/proto-gen-go.git/project_requirement_api"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

type KanoStatDbItem struct {
	Excite   float32 `bson:"excite"`
	Expect   float32 `bson:"expect"`
	Basic    float32 `bson:"basic"`
	Nodiff   float32 `bson:"nodiff"`
	Reverse  float32 `bson:"reverse"`
	Dubiouse float32 `bson:"dubiouse"`
}

type BaseRequirementInfoDbItem struct {
	Title     string   `bson:"title"`
	Content   string   `bson:"content"`
	TagIdList []string `bson:"tagIdList"`
}

type RequirementInfoDbItem struct {
	RequirementId       string                    `bson:"_id"`
	ProjectId           string                    `bson:"projectId"`
	BaseInfo            BaseRequirementInfoDbItem `bson:"baseInfo"`
	IssueLinkCount      uint32                    `bson:"issueLinkCount"`
	Closed              bool                      `bson:"closed"`
	KanoStat            KanoStatDbItem            `bson:"kanoStat"`
	KanoBetter          float32                   `bson:"kanoBetter"`
	FourQUrgencyValue   float32                   `bson:"fourQUrgency"`
	FourQImportantValue float32                   `bson:"fourQImportant"`
	CreateUserId        string                    `bson:"createUserId"`
	CreateTime          int64                     `bson:"createTime"`
	UpdateUserId        string                    `bson:"updateUserId"`
	UpdateTime          int64                     `bson:"updateTime"`
}

func (item *RequirementInfoDbItem) ToRequirementInfo() *project_requirement_api.RequirementInfo {
	tagIdList := []string{}
	if item.BaseInfo.TagIdList != nil {
		tagIdList = item.BaseInfo.TagIdList
	}
	return &project_requirement_api.RequirementInfo{
		RequirementId: item.RequirementId,
		ProjectId:     item.ProjectId,
		BaseInfo: &project_requirement_api.BaseRequirementInfo{
			Title:     item.BaseInfo.Title,
			Content:   item.BaseInfo.Content,
			TagIdList: tagIdList,
		},
		IssueLinkCount:      item.IssueLinkCount,
		CreateUserId:        item.CreateUserId,
		CreateTime:          item.CreateTime,
		UpdateUserId:        item.UpdateUserId,
		UpdateTime:          item.UpdateTime,
		Closed:              item.Closed,
		KanoExciteValue:     item.KanoStat.Excite,
		KanoExpectValue:     item.KanoStat.Expect,
		KanoBasicValue:      item.KanoStat.Basic,
		KanoNodiffValue:     item.KanoStat.Nodiff,
		KanoReverseValue:    item.KanoStat.Reverse,
		KanoDubiouseValue:   item.KanoStat.Dubiouse,
		FourQUrgencyValue:   item.FourQUrgencyValue,
		FourQImportantValue: item.FourQImportantValue,
		TagInfoList:         []*project_requirement_api.RequirementTag{},
		WatchUserList:       []*project_requirement_api.WatchUser{},
	}
}

type LinkRequirementParam struct {
	ProjectId            string
	FilterByKeyword      bool
	Keyword              string
	FilterByHasLinkIssue bool
	HasLinkIssue         bool
	FilterByClosed       bool
	Closed               bool
	FilterByTagIdList    bool
	TagIdList            []string
	FilterByWatch        bool
	WatchReqIdList       []string
}

func (param *LinkRequirementParam) genQuery() bson.M {
	query := bson.M{"projectId": param.ProjectId}
	if param.FilterByKeyword {
		query["baseInfo.title"] = primitive.Regex{
			Pattern: regexp.QuoteMeta(param.Keyword),
			Options: "i",
		}
	}
	if param.FilterByHasLinkIssue {
		if param.HasLinkIssue {
			query["issueLinkCount"] = bson.M{"$gt": 0}
		} else {
			query["issueLinkCount"] = 0
		}
	}
	if param.FilterByClosed {
		query["closed"] = param.Closed
	}
	if param.FilterByTagIdList {
		query["baseInfo.tagIdList"] = bson.M{"$in": param.TagIdList}
	}
	if param.FilterByWatch {
		query["_id"] = bson.M{"$in": param.WatchReqIdList}
	}
	return query
}

type _RequirementInfoDao struct {
	mc *mongo.Collection
}

func newRequirementInfoDao(db *mongo.Database) (*_RequirementInfoDao, error) {
	mc := db.Collection(db_ns.DB_NS_PROJECT_REQUIRE_MENT + "req_info")
	_, err := mc.Indexes().CreateOne(context.Background(), mongo.IndexModel{
		Keys: bson.D{{"projectId", 1}, {"updateTime", 1}},
	})
	if err != nil {
		return nil, err
	}
	return &_RequirementInfoDao{mc: mc}, nil
}

func (db *_RequirementInfoDao) Insert(ctx context.Context, dbItem *RequirementInfoDbItem) error {
	_, err := db.mc.InsertOne(ctx, dbItem)
	return err
}

func (db *_RequirementInfoDao) Count(ctx context.Context, param *LinkRequirementParam) (uint32, error) {
	count, err := db.mc.CountDocuments(ctx, param.genQuery())
	return uint32(count), err
}

func (db *_RequirementInfoDao) List(ctx context.Context, param *LinkRequirementParam, offset, limit int64, sortType project_requirement_api.REQ_SORT_TYPE) ([]*RequirementInfoDbItem, error) {
	sort := bson.D{{"updateTime", -1}}
	if sortType == project_requirement_api.REQ_SORT_TYPE_REQ_SORT_UPDATE_TIME {
		sort = bson.D{{"updateTime", -1}}
	} else if sortType == project_requirement_api.REQ_SORT_TYPE_REQ_SORT_CREATE_TIME {
		sort = bson.D{{"createTime", -1}}
	} else if sortType == project_requirement_api.REQ_SORT_TYPE_REQ_SORT_KANO {
		sort = bson.D{{"kanoBetter", -1}, {"updateTime", -1}}
	} else if sortType == project_requirement_api.REQ_SORT_TYPE_REQ_SORT_URGENT {
		sort = bson.D{{"fourQUrgency", -1}, {"updateTime", -1}}
	} else if sortType == project_requirement_api.REQ_SORT_TYPE_REQ_SORT_IMPORTANT {
		sort = bson.D{{"fourQImportant", -1}, {"updateTime", -1}}
	}
	cursor, err := db.mc.Find(ctx, param.genQuery(), options.Find().SetSkip(offset).SetLimit(limit).SetSort(sort))
	if err != nil {
		return nil, err
	}
	retList := []*RequirementInfoDbItem{}
	err = cursor.All(ctx, &retList)
	return retList, err
}

func (db *_RequirementInfoDao) ListById(ctx context.Context, projectId string, requirementIdList []string) ([]*RequirementInfoDbItem, error) {
	if len(requirementIdList) == 0 {
		return []*RequirementInfoDbItem{}, nil
	}
	cursor, err := db.mc.Find(ctx, bson.M{"_id": bson.M{"$in": requirementIdList}, "projectId": projectId})
	if err != nil {
		return nil, err
	}
	retList := []*RequirementInfoDbItem{}
	err = cursor.All(ctx, &retList)
	return retList, err
}

func (db *_RequirementInfoDao) DistinctRequirementIdByProject(ctx context.Context, projectId string) ([]string, error) {
	tmpList, err := db.mc.Distinct(ctx, "_id", bson.M{"projectId": projectId})
	if err != nil {
		return nil, err
	}
	retList := []string{}
	for _, tmpValue := range tmpList {
		if tmpValue.(string) != "" {
			retList = append(retList, tmpValue.(string))
		}
	}
	return retList, nil
}

func (db *_RequirementInfoDao) Exist(ctx context.Context, projectId, requirementId string) (bool, error) {
	count, err := db.mc.CountDocuments(ctx, bson.M{"_id": requirementId, "projectId": projectId})
	return count > 0, err
}

func (db *_RequirementInfoDao) Get(ctx context.Context, projectId, requirementId string) (*RequirementInfoDbItem, error) {
	retItem := &RequirementInfoDbItem{}
	err := db.mc.FindOne(ctx, bson.M{"_id": requirementId, "projectId": projectId}).Decode(retItem)
	return retItem, err
}

func (db *_RequirementInfoDao) Remove(ctx context.Context, projectId, requirementId string) error {
	_, err := db.mc.DeleteOne(ctx, bson.M{"_id": requirementId, "projectId": projectId})
	return err
}

func (db *_RequirementInfoDao) UpdateBaseInfo(ctx context.Context, projectId, requirementId string, dbItem *BaseRequirementInfoDbItem, updateUserId string, updateTime int64) error {
	_, err := db.mc.UpdateOne(ctx, bson.M{"_id": requirementId, "projectId": projectId},
		bson.M{"$set": bson.M{"baseInfo": dbItem, "updateUserId": updateUserId, "updateTime": updateTime}})
	return err
}

func (db *_RequirementInfoDao) UpdateTagIdList(ctx context.Context, projectId, requirementId string, tagIdList []string, updateUserId string, updateTime int64) error {
	_, err := db.mc.UpdateOne(ctx, bson.M{"_id": requirementId, "projectId": projectId},
		bson.M{"$set": bson.M{"baseInfo.tagIdList": tagIdList, "updateUserId": updateUserId, "updateTime": updateTime}})
	return err
}

func (db *_RequirementInfoDao) UpdateIssueLinkCount(ctx context.Context, projectId, requirementId string, count uint32) error {
	_, err := db.mc.UpdateOne(ctx, bson.M{"_id": requirementId, "projectId": projectId},
		bson.M{"$set": bson.M{"issueLinkCount": count}})
	return err
}

func (db *_RequirementInfoDao) GetTitleMapById(ctx context.Context, projectId string, requirementIdList []string) (map[string]string, error) {
	query := bson.M{"_id": bson.M{"$in": requirementIdList}}
	if projectId != "" {
		query["projectId"] = projectId
	}
	cursor, err := db.mc.Find(ctx, query,
		options.Find().SetProjection(bson.M{"_id": 1, "baseInfo.title": 1}))
	if err != nil {
		return nil, err
	}
	tmpList := []*RequirementInfoDbItem{}
	err = cursor.All(ctx, &tmpList)
	if err != nil {
		return nil, err
	}
	retMap := map[string]string{}
	for _, tmpItem := range tmpList {
		retMap[tmpItem.RequirementId] = tmpItem.BaseInfo.Title
	}
	return retMap, nil
}

func (db *_RequirementInfoDao) UpdateClosed(ctx context.Context, projectId, requirementId string, closed bool, updateUserId string, updateTime int64) error {
	_, err := db.mc.UpdateOne(ctx, bson.M{"_id": requirementId, "projectId": projectId}, bson.M{"$set": bson.M{
		"closed":       closed,
		"updateUserId": updateUserId,
		"updateTime":   updateTime,
	}})
	return err
}

func (db *_RequirementInfoDao) UpdateKanoState(ctx context.Context, projectId, requirementId string, dbItem *KanoStatDbItem) error {
	betterValue := float32(0.0)
	totalValue := dbItem.Excite + dbItem.Expect + dbItem.Basic + dbItem.Nodiff
	if totalValue >= 0.00002 {
		betterValue = (dbItem.Excite + dbItem.Expect) / totalValue
	}
	_, err := db.mc.UpdateOne(ctx, bson.M{"_id": requirementId, "projectId": projectId}, bson.M{"$set": bson.M{
		"kanoStat":   dbItem,
		"kanoBetter": betterValue,
	}})
	return err
}

func (db *_RequirementInfoDao) UpdateFourQState(ctx context.Context, projectId, requirementId string, urgencyValue, importantValue float32) error {
	_, err := db.mc.UpdateOne(ctx, bson.M{"_id": requirementId, "projectId": projectId}, bson.M{"$set": bson.M{
		"fourQUrgency":   urgencyValue,
		"fourQImportant": importantValue,
	}})
	return err
}

// 修复状态和closed为空的数据，版本升级的时候用到
func (db *_RequirementInfoDao) FixStateAndCloseFlag(ctx context.Context) error {
	_, err := db.mc.UpdateMany(ctx, bson.M{}, bson.M{"$set": bson.M{
		"closed":         false,
		"kanoStat":       &KanoStatDbItem{},
		"kanoBetter":     0.0,
		"fourQUrgency":   0.0,
		"fourQImportant": 0.0,
	}})
	return err
}
